
package todolist.logic.task;

import java.util.Date;
import java.util.ListIterator;
import todolist.entity.*;
import todolist.logic.parser.*;
import todolist.personalization.ErrorMessages;
import todolist.personalization.PriorityCustomizer;
import todolist.util.DateUtils;
import todolist.util.Utils;

/**
 * Controller facade for Task core subsystem, Singleton class.
 * @author You Li
 * @author Lina
 */
public class TaskController {
	
	// handle singleton instance

	private static TaskController _objTC;

	public static TaskController getInstance() {
		if (_objTC == null)
			_objTC = new TaskController();
		return _objTC;
	}
	
	// member attributes

	private TaskContainerEntity _entTCE;
	private StringBuffer _objSB;
	private ReminderController _objRC;
	private EmailEntity _entEmail;
	
	private TaskController() {
		_entTCE = new TaskContainerEntity();
		_objSB = new StringBuffer();
		_objRC = new ReminderController(_entTCE);
		_entEmail = new EmailEntity("nus.st1112.cs2103@gmail.com", "ST1112CS2103");
		
		// TODO : import tasks comes first before the calling of other functions otherwise container will be empty.
		updateInternalListAll();
		_objRC.setReminder();
		//_entEmail.sendEmail("nus.st1112.cs2103@gmail.com", "Auto Email: To-Do-List Daily Tasks Overview", getDailyTasksOverview());
	}

	// business facilitator methods
	
	/**
	 * Adds a Task and returns the newly created/added Task.
	 * Assumes all input fields have been validated.
	 * @throws IllegalArgumentException
	 */
	public void addTask(
		String pstrName, String pstrDT, PriorityType penmPriority, String pstrReminder, String pstrComments)
		throws IllegalArgumentException {
		// TODO : extract reminder to another package; ensure after extraction this method is O(1)

		// parse DT
		DateTimeParser objDTParser = new DateTimeParser();
		DateTimeParseResult objParseResult = objDTParser.parseDTDescription(pstrDT);
		if (objParseResult.getError() != null)
			throw new IllegalArgumentException("TaskController.addTask(): Failed to parse Date/Time.");
		DateTimeEntity entStartDT = objParseResult.getStartDT();
		DateTimeEntity entEndDT = objParseResult.getEndDT();
		
		// parse RO
		ReminderOffsetParser objROP = new ReminderOffsetParser();
		ReminderOffsetParseResult objROPResult = objROP.parseReminderOffset(pstrReminder);
		if (objROPResult.getError() != null)
			throw new IllegalArgumentException("TaskController.addTask(): Failed to parse Reminder Offset.");
		
		// create Task Entity
		long lngTaskID = _entTCE.getLastTaskId() + 1;
		TaskEntity entNewTask = new TaskEntity(lngTaskID, pstrName);
		entNewTask.setStartDT(entStartDT);
		entNewTask.setEndDT(entEndDT);
		if (penmPriority != null)
			entNewTask.setPriority(penmPriority);
		entNewTask.setTaskComments(pstrComments);
		entNewTask.setReminderOffset(objROPResult.getReminderOffset());
		
		// add Task Entity
		_entTCE.addTask(entNewTask);

		// add short details to front of list all buffer
		StringBuffer objTempSB = new StringBuffer();
		objTempSB.append(entNewTask.toShortString() + Utils.getLineSeparator());
		objTempSB.append(_objSB);
		_objSB = objTempSB;
		
		_objRC.setReminder();
	}

	/**
	 * Edits a Task. O(n).
	 * Assumes all input fields have been validated.
	 * @throws IllegalArgumentException
	 */
	public void editTask(long plngTaskId, TaskFieldType penmField, String pstrValue) throws IllegalArgumentException {
		// TODO : extract reminder to another package
		TaskEntity entEditTask = _entTCE.findTask(plngTaskId);
		if (entEditTask == null)
			throw new IllegalArgumentException("TaskController.editTask(): Task with given ID not found.");
		switch(penmField) {
			case TASK_FIELD_TASKNAME:
				entEditTask.setTaskName(pstrValue);
				break;
			case TASK_FIELD_DT:
				DateTimeParser objDTParser = new DateTimeParser();
				DateTimeParseResult objDTPResult = objDTParser.parseDTDescription(pstrValue);
				if (objDTPResult.getError() != null)
					throw new IllegalArgumentException("TaskController.editTask(): Failed to parse Date/Time.");
				entEditTask.setBothDT(objDTPResult.getStartDT(), objDTPResult.getEndDT());
				_objRC.setReminder();
				break;
			case TASK_FIELD_PRIORITY:
				PriorityParser objPP = new PriorityParser();
				PriorityType enmPriority = objPP.parsePriority(pstrValue);
				if (enmPriority == null)
					enmPriority = PriorityCustomizer.getInstance().getDefaultPriorityType();
				entEditTask.setPriority(enmPriority);
				break;
			case TASK_FIELD_COMMENTS:
				entEditTask.setTaskComments(pstrValue);
				break;
			case TASK_FIELD_REMINDER:
				ReminderOffsetParser objROP = new ReminderOffsetParser();
				ReminderOffsetParseResult objROPResult = objROP.parseReminderOffset(pstrValue);
				if (objROPResult.getError() != null)
					throw new IllegalArgumentException("TaskController.editTask(): Failed to parse Reminder Offset.");
				entEditTask.setReminderOffset(objROPResult.getReminderOffset());
				_objRC.setReminder();
				break;
			default: // shouldn't happen, Field Type null or set Status
				throw new IllegalArgumentException("TaskController.editTask(): Invalid Task Field Type.");
		}
		updateInternalListAll();
	}
	
	/**
	 * Deletes a Task. O(n).
	 * @throws IllegalArgumentException
	 */
	public void deleteTask(long plngTaskId) throws IllegalArgumentException {
		// TODO : extract reminder to another package
		_objRC.setReminder();
		if (_entTCE.deleteTask(plngTaskId)) // if Task found and deleted
			updateInternalListAll();
		else
			throw new IllegalArgumentException("TaskController.deleteTask(): Task with given ID not found.");
	}
	
	/**
	 * Lists short status of each Task, for display. O(1).
	 * If there are no Tasks, empty String returned.
	 */
	public String getListAllString() {
		return _objSB.toString();
	}
	
	/**
	 * Returns full details of Task by Task ID, or empty String if Task not found.
	 * Assumes Task ID represents an existing Task.
	 * @throws IllegalArgumentException
	 */
	public String getShowDetailsString(long plngTaskId) throws IllegalArgumentException {
		TaskEntity entTask = _entTCE.findTask(plngTaskId);
		if (entTask == null)
			throw new IllegalArgumentException("TaskController.getShowDetailsString(): Task with given ID not found.");
		return entTask.toString();
	}

	/**
	 * Returns full details of Tasks matched by name/comments, or empty String if no results. O(Qn).
	 * @throws IllegalArgumentException
	 */
	public String getSearchResultsString(String pstrQuery) throws IllegalArgumentException {
		if (pstrQuery == null)
			throw new IllegalArgumentException("TaskController.getSearchResultsString(): Search query is mandatory.");
		String[] arrQuery = pstrQuery.split("\\s+");
		ListIterator <TaskEntity> objItr = _entTCE.getListIterator();
		String strResult = "";
		while (objItr.hasNext()) {
			TaskEntity entTask = objItr.next();
			for (String strCurrQuery : arrQuery) { // O(Q) each
				if (entTask.getTaskName().toUpperCase().contains(strCurrQuery.toUpperCase()) ||
					entTask.getTaskComments().toUpperCase().contains(strCurrQuery.toUpperCase())) {
					strResult += entTask.toString() + Utils.getLineSeparator();
					break; // this task added, move on to next task; yes, break, not continue
				}
			}
		}
		return strResult;
	}
	
	/**
	 * Sorts Task Container by given Property and Direction. O(n log n).
	 * @throws IllegalArgumentException
	 */
	public void sort(TaskFieldType penmField, SortDirectionType penmSortDirection) throws IllegalArgumentException {
		if (penmSortDirection == null || penmField == null)
			throw new IllegalArgumentException("TaskController.sort(): Sort requires Direction and Type.");
		if (penmSortDirection == SortDirectionType.SORT_DESC)
			switch (penmField) {
				case TASK_FIELD_TASKNAME:
					_entTCE.sortTasks(new TaskNameDescending()); break;
				case TASK_FIELD_DT:
					_entTCE.sortTasks(new EndDateDescending()); break;
				case TASK_FIELD_PRIORITY:
					_entTCE.sortTasks(new PriorityDescending()); break;
				case TASK_FIELD_STATUS:
					_entTCE.sortTasks(new TaskStatusDescending()); break;
				default: // shouldn't happen =X
					throw new IllegalArgumentException("Invalid sort Type.");
			}
		else if (penmSortDirection == SortDirectionType.SORT_ASC)
			switch (penmField) {
				case TASK_FIELD_TASKNAME:
					_entTCE.sortTasks(new TaskNameAscending()); break;
				case TASK_FIELD_DT:
					_entTCE.sortTasks(new EndDateAscending()); break;
				case TASK_FIELD_PRIORITY:
					_entTCE.sortTasks(new PriorityAscending()); break;
				case TASK_FIELD_STATUS:
					_entTCE.sortTasks(new TaskStatusAscending()); break;
				default: // shouldn't happen =X
					throw new IllegalArgumentException("Invalid sort Type.");
			}			
		else // shouldn't happen =X
			throw new IllegalArgumentException("Invalid sort Direction.");
		updateInternalListAll(); // + O(n)
	}
	
	/**
	 * Returns full details of all Tasks due/starting today. O(n).
	 */
	public String getTodaysTasksString(){
		Date dtmToday = DateUtils.removeTimeFromDate(new Date());
		StringBuffer objSB = new StringBuffer();
		ListIterator<TaskEntity> objItr = _entTCE.getListIterator();
		while(objItr.hasNext()) {
			TaskEntity entTask = objItr.next();
			Date dtmStart = null;
			Date dtmEnd = null;
			if (entTask.getStartDT() != null)
				dtmStart = DateUtils.removeTimeFromDate(entTask.getStartDT().getInternalDate());
			if (entTask.getEndDT() != null)
				dtmEnd = DateUtils.removeTimeFromDate(entTask.getEndDT().getInternalDate());
			if (dtmToday.equals(dtmStart) || dtmToday.equals(dtmEnd)) {
				objSB.append(entTask.toString());
				objSB.append(Utils.getLineSeparator());
			}
		}
		return objSB.toString();
	}
	
	// business validate methods

	/**
	 * Returns null if Task ID is valid, or Error Message otherwise.
	 */
	public String validateExistingTaskID(String pstrTaskID){
		if (pstrTaskID == null || pstrTaskID.trim().equals(""))
			return ErrorMessages.ERR_INVALID_TASK_ID;
		long lngTaskID;
		try {
			lngTaskID = Long.valueOf(pstrTaskID.trim());
		} catch (NumberFormatException objEx) {
			return ErrorMessages.ERR_INVALID_TASK_ID;
		}
		if (lngTaskID <= 0 || _entTCE.findTask(lngTaskID) == null)
			return ErrorMessages.ERR_INVALID_TASK_ID;
		return null;
	}

	/**
	 * Returns null if Task Name is valid, or Error Message otherwise.
	 */
	public String validateTaskName(String pstrTaskName){
		if(pstrTaskName == null || pstrTaskName.trim().equals("") ||
			pstrTaskName.contains(Utils.getLineSeparator()))
			return ErrorMessages.ERR_INVALID_TASK_NAME;
		return null;
	}
	
	/**
	 * Returns null if Date is valid, or Error Message otherwise.
	 */
	public String validateDT(String pstrDT) {
		DateTimeParser objDTParser = new DateTimeParser();
		DateTimeParseResult objParseResult = objDTParser.parseDTDescription(pstrDT);
		return objParseResult.getError();
	}

	/**
	 * Returns null if Priority is valid or not specified, or Error Message otherwise.
	 */
	public String validatePriority(String pstrPriority) {
		if(pstrPriority == null || pstrPriority.trim().equals(""))
			return null; // optional field, if not specified
		PriorityParser objPP = new PriorityParser();
		boolean blnPriorityNotFound = (objPP.parsePriority(pstrPriority) == null);
		if (blnPriorityNotFound)
			return ErrorMessages.ERR_INVALID_PRIORITY;
		return null;
	}

	/**
	 * Returns null if Task Comments is valid, or Error Message otherwise.
	 */
	public String validateTaskComments(String pstrComments){
		if(pstrComments == null || pstrComments.contains(Utils.getLineSeparator()))
			return ErrorMessages.ERR_INVALID_COMMENTS;
		return null;
	}

	/**
	 * Returns null if Reminder Offset is valid, or Error Message otherwise.
	 */
	public String validateReminder(String pstrOffset) {
		ReminderOffsetParser objROP = new ReminderOffsetParser();
		ReminderOffsetParseResult objParseResult = objROP.parseReminderOffset(pstrOffset);
		return objParseResult.getError();
	}
	
	 // TODO : v0.x - recurring events
	
	/*
	public boolean validateRecurring(String pstrRecurring) {
		if (pstrRecurring.equalsIgnoreCase("Yes") ||
			pstrRecurring.equalsIgnoreCase("No"))
			return true;
		return false;
	}
	
	public boolean validateRFrequency(String rFrequency){
		if (rFrequency.equalsIgnoreCase("Daily")
				|| rFrequency.equalsIgnoreCase("Weekly")
				|| rFrequency.equalsIgnoreCase("Bi-weekly")
				|| rFrequency.equalsIgnoreCase("Monthly")
				|| rFrequency.equalsIgnoreCase("Annually"))
			return true;
		return false;
	}*/
	
	// private methods

	/**
	 * Updates List All buffer with short status of each Task in Container, for display. O(n).
	 */
	private void updateInternalListAll() {
		_objSB.setLength(0); // clear list all buffer
		ListIterator<TaskEntity> objItr = _entTCE.getListIterator();
		while(objItr.hasNext()) {
			TaskEntity entTask = objItr.next();
			_objSB.append(entTask.toShortString() + Utils.getLineSeparator());
		}
	}
	
}
